/*
 * Copyright (c) 2013 - 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2017 NXP
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "app_inc.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/

/*******************************************************************************
 * Variables
 ******************************************************************************/
char gAppPrintfBuffer[256];
volatile uint32_t gAppCameraVsyncStartCounter;
volatile uint32_t gAppCameraVsyncEndCounter;
volatile uint32_t gAppCameraHrefStartCounter;
volatile uint32_t gAppCameraHrefEndCounter;
volatile uint32_t gAppCameraPclkStartCounter;

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
void App_BootClockRUN(void);
void App_Tick_PeriodicalCallback(void);

void Camera_Init(void);
void Camera_StartCapture(void);
void Camera_StopCapture(void);
/*******************************************************************************
 * Code
 ******************************************************************************/
/*!
 * @brief Main function
 */
int main(void)
{
    /* Init board hardware. */
    BOARD_InitBootPins();
    BOARD_BootClockRUN();
    App_BootClockRUN(); /* Enable the clocks additionally not in BOARD_BootClockRUN(). */

    /* Initialize debug console. */
    BOARD_InitDebugConsole(); /* Flexcomm0 - UART0, 115200. */

    PRINTF("\r\nApp_DMA_Camera Demo.\r\n");
    PRINTF("build time: %s, %s\r\n", __TIME__, __DATE__);

    OLED_Init();
    //OLED_PutStrP6x8(0U, 0U, "HelloWorld. 1234");
    OLED_PrintReset();
    OLED_PrintStr("Camera Demo.\r\n");

    Tick_Init(200UL); /* 5ms per tick. */
    BTN_Init();
    Tick_InstallPeriodicalCallback(1U, App_Tick_PeriodicalCallback);

    Camera_Init();

    while (1)
    {
        OLED_ClearScreen(0U);
        OLED_PrintReset();
        sprintf(gAppPrintfBuffer, "VSYNC START: %d\r\n"
                                  "VSYNC END  : %d\r\n"
                                  "HREF  START: %d\r\n"
                                  "HREF  END  : %d\r\n"
                                  "PCLK  START: %d\r\n" ,
            gAppCameraVsyncStartCounter,
            gAppCameraVsyncEndCounter,
            gAppCameraHrefStartCounter,
            gAppCameraHrefEndCounter,
            gAppCameraPclkStartCounter
            );
        OLED_PrintStr(gAppPrintfBuffer);
        BTN_WaitBlocking(0U); /* 等待KEY0按下并松开 */
    }
}

/* 启用在BOARD_BootClockRUN()函数中未启用的时钟 */
void App_BootClockRUN(void)
{
    CLOCK_EnableClock(kCLOCK_Gpio0);
    CLOCK_EnableClock(kCLOCK_Gpio1);
    CLOCK_EnableClock(kCLOCK_InputMux);
    CLOCK_EnableClock(kCLOCK_Sct0);
}

/* 由Systick周期调用的回调函数 */
void App_Tick_PeriodicalCallback(void)
{
    BTN_WaitBlockingHook();
}

#define APP_SCT_EVENT_VSYNC_START 0U /* IN0下降沿 */
#define APP_SCT_EVENT_VSYNC_END   1U /* IN0上升沿 */
#define APP_SCT_EVENT_HREF_START  2U /* IN1上升沿 */
#define APP_SCT_EVENT_HREF_END    3U /* IN1下降沿 */
#define APP_SCT_EVENT_PCLK_START  4U /* IN2上升沿 */

#define APP_SCT_STATE_WAIT_NEW_FRAME  0U
#define APP_SCT_STATE_WAIT_NEW_LINE   1U
#define APP_SCT_STATE_WAIT_NEW_PCLK   2U
#define APP_SCT_STATE_WAIT_NEXT_FRAME 3U

#define APP_SCT_INPUT_LINE_VSYNC  0U
#define APP_SCT_INPUT_LINE_HREF   1U
#define APP_SCT_INPUT_LINE_PCLK   2U

void App_SCT_Configuration(void)
{
    gAppCameraVsyncStartCounter = 0U;
    gAppCameraVsyncEndCounter = 0U;
    gAppCameraHrefStartCounter = 0U;
    gAppCameraHrefEndCounter = 0U;
    gAppCameraPclkStartCounter = 0U;

    RESET_PeripheralReset(kSCT0_RST_SHIFT_RSTn);

    /*
      Camera   IO         PinMux      SCT line
    * VSYNC -> PIO0_13 -> SCT0_GPI0 -> IN0
    * HREF  -> PIO0_14 -> SCT0_GPI1 -> IN1
    * PCLK  -> PIO0_17 -> SCT0_GPI7 -> IN2
    */
    INPUTMUX_AttachSignal(INPUTMUX, 0U, kINPUTMUX_SctGpi0ToSct0); /* IN0. */
    INPUTMUX_AttachSignal(INPUTMUX, 1U, kINPUTMUX_SctGpi1ToSct0); /* IN1. */
    INPUTMUX_AttachSignal(INPUTMUX, 2U, kINPUTMUX_SctGpi7ToSct0); /* IN2. */

    /* 在Camera的case中仅使用STATE_L */
    SCT0->CONFIG = SCT_CONFIG_UNIFY(0) /* 双定时器模式 */
                 | SCT_CONFIG_CLKMODE(0) /* 使用System Clock驱动整个SCT */
                 | SCT_CONFIG_INSYNC(0x3FF) /* 启用同步模式，SCT本身的时钟主频比较快，为220MHz */
                 ;


    SCT0->STATE = SCT_STATE_STATE_L(0U); /* 还原为State0 */

    /* 设定在不同状态下可以使能的事件 */
    /* APP_SCT_EVENT_VSYNC_START事件仅在如下状态下被需要：
     * - APP_SCT_STATE_WAIT_NEW_FRAME
     * - APP_SCT_STATE_WAIT_NEXT_FRAME (optional)
     */
    SCT0->EVENT[APP_SCT_EVENT_VSYNC_START].STATE = (1U << APP_SCT_STATE_WAIT_NEW_FRAME )
                                                 | (1U << APP_SCT_STATE_WAIT_NEXT_FRAME)
                                                 ;
    /* APP_SCT_EVENT_HREF_START事件仅在如下状态被需要：
     * - APP_SCT_STATE_WAIT_NEW_LINE
     */
    SCT0->EVENT[APP_SCT_EVENT_HREF_START].STATE  = (1U << APP_SCT_STATE_WAIT_NEW_LINE  )
                                                 ;
    SCT0->EVENT[APP_SCT_EVENT_PCLK_START].STATE  = (1U << APP_SCT_STATE_WAIT_NEW_PCLK  )
                                                 ;
    /* 在任何状态下捕获APP_SCT_EVENT_VSYNC_END都将切换回APP_SCT_STATE_WAIT_NEW_FRAME状态
     * 只是考虑开始捕获摄像头同步信号时，通信帧不完整。
     * 通过捕获VSYNC结束事件初始化整个状态机
     */
    SCT0->EVENT[APP_SCT_EVENT_VSYNC_END  ].STATE = (1U << APP_SCT_STATE_WAIT_NEW_FRAME )
                                                 | (1U << APP_SCT_STATE_WAIT_NEW_LINE  )
                                                 | (1U << APP_SCT_STATE_WAIT_NEW_PCLK  )
                                                 | (1U << APP_SCT_STATE_WAIT_NEXT_FRAME)
                                                 ;
    /* 在任何状态下捕获APP_SCT_EVENT_VSYNC_END都将切换回APP_SCT_STATE_WAIT_NEW_FRAME状态 */
    SCT0->EVENT[APP_SCT_EVENT_HREF_END  ].STATE  = //(1U << APP_SCT_STATE_WAIT_NEW_FRAME )
                                                   //(1U << APP_SCT_STATE_WAIT_NEW_LINE  )
                                                   (1U << APP_SCT_STATE_WAIT_NEW_PCLK  )
                                                 //| (1U << APP_SCT_STATE_WAIT_NEXT_FRAME)
                                                 ;

    /* 设定每个事件的操作 */
    /* VSYNC开始事件：
     * - APP_SCT_INPUT_LINE_VSYNC输入信号线下降沿触发事件发生
     * - 事件发生后切换到状态APP_SCT_STATE_WAIT_NEW_LINE，准备接收新的一行
     */
    SCT0->EVENT[APP_SCT_EVENT_VSYNC_START].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(APP_SCT_INPUT_LINE_VSYNC) /* 接受VSYNC输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(2) /* 触发时机为输入下降沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(APP_SCT_STATE_WAIT_NEW_LINE) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* HREF开始事件：
     * - APP_SCT_INPUT_LINE_HREF输入信号线上升沿触发事件发生
     * - 事件发生后切换到状态APP_SCT_STATE_WAIT_NEW_PCLK，准备接收新的像素点数据
     */
    SCT0->EVENT[APP_SCT_EVENT_HREF_START ].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(APP_SCT_INPUT_LINE_HREF) /* 接受HREF输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(1) /* 触发时机为输入上升沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(APP_SCT_STATE_WAIT_NEW_PCLK) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* PCLK开始事件：
     * - APP_SCT_INPUT_LINE_PCLK输入信号线上升沿触发事件发生
     * - 事件发生后切换到状态APP_SCT_STATE_WAIT_NEW_PCLK（本状态），准备接收新的像素点数据
     */
    SCT0->EVENT[APP_SCT_EVENT_PCLK_START ].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(APP_SCT_INPUT_LINE_PCLK) /* 接受HREF输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(1) /* 触发时机为输入上升沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(APP_SCT_STATE_WAIT_NEW_PCLK) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* HREF结束事件：
     * - APP_SCT_INPUT_LINE_HREF输入信号下降沿触发事件发生
     * - 事件发生后切换到状态APP_SCT_STATE_WAIT_NEW_LINE，准备开始接收下一行数据
     */
    SCT0->EVENT[APP_SCT_EVENT_HREF_END   ].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(APP_SCT_INPUT_LINE_HREF) /* 接受HREF输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(2) /* 触发时机为输入下降沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(APP_SCT_STATE_WAIT_NEW_LINE) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* VSYNC结束事件：
     * - APP_SCT_INPUT_LINE_VSYNC输入信号上升沿触发事件发生
     * - 事件发生后切换到状态APP_SCT_STATE_WAIT_NEW_FRAME，准备开始接收下一帧数据
     */
    SCT0->EVENT[APP_SCT_EVENT_VSYNC_END  ].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(APP_SCT_INPUT_LINE_VSYNC) /* 接受VSYNC输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(1) /* 触发时机为输入上升沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(APP_SCT_STATE_WAIT_NEW_FRAME) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* 设定DMA0的触发信号 */
    //SCT0->DMA0REQUEST = SCT_DMA0REQUEST_DEV_0(1U << APP_SCT_EVENT_PCLK_START); /* State4对应PCLK开始 */

    /* 设定可以触发中断的事件 */
    SCT0->EVEN = (1U << APP_SCT_EVENT_VSYNC_START) /* 对场次进行计数 */
               //| (1U << APP_SCT_EVENT_VSYNC_END  )
               //| (1U << APP_SCT_EVENT_HREF_START )
               | (1U << APP_SCT_EVENT_HREF_END   )  /* 重新装载描述符 */
               //| (1U << APP_SCT_EVENT_PCLK_START )
               ;
    NVIC_EnableIRQ(SCT0_IRQn);

    /* 启动SCT */
    SCT0->EVFLAG = (uint32_t)(-1); /* 清标志位 */
    SCT0->CTRL &= ~SCT_CTRL_HALT_L_MASK;
}

/* SCT中断服务程序入口，仅用于调试 */
void SCT0_IRQHandler(void)
{
    uint32_t eventFlags = SCT0->EVFLAG;

#if 1
    if (0U != (eventFlags & (1U << APP_SCT_EVENT_VSYNC_START)))
    {
        gAppCameraVsyncStartCounter++;
    }
#endif
#if 0
    if (0U != (eventFlags & (1U << APP_SCT_EVENT_VSYNC_END)))
    {
        gAppCameraVsyncEndCounter++;
        if (gAppCameraVsyncStartCounter >= 1U)
        {
            Camera_StopCapture();
        }
    }
#endif
#if 0
    if (0U != (eventFlags & (1U << APP_SCT_EVENT_HREF_START)))
    {
        gAppCameraHrefStartCounter++;
    }
#endif
#if 1
    if (0U != (eventFlags & (1U << APP_SCT_EVENT_HREF_END)))
    {
        gAppCameraHrefEndCounter++;
    }
#endif
#if 0
    if (0U != (eventFlags & (1U << APP_SCT_EVENT_PCLK_START)))
    {
        gAppCameraPclkStartCounter++;
    }
#endif
    SCT0->EVFLAG = eventFlags;
}

void App_DMA_Configuration(void)
{

}

void Camera_Init(void)
{
    App_SCT_Configuration();
    App_DMA_Configuration();
}

void Camera_StartCapture(void)
{
    SCT0->EVFLAG = (uint32_t)(-1); /* 清标志位 */
    SCT0->CTRL &= ~SCT_CTRL_HALT_L_MASK;
}

void Camera_StopCapture(void)
{
    SCT0->CTRL |= SCT_CTRL_HALT_L_MASK;
}

/* EOF. */
